package com.qd.panda.gas.station.shell.last;


import cn.hutool.crypto.SecureUtil;
import com.cdqidi.exception.ApiException;
import com.cdqidi.util.JacksonUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.qd.common.panda.domain.enums.GasStationCompanyNameEnum;
import com.qd.common.sys.domain.entity.log.SendLogDTO;
import com.qd.panda.gas.station.base.BaseHttpResponseUtil;
import com.qd.panda.gas.station.base.GasHttpService;
import com.qd.panda.gas.station.req.*;
import com.qd.panda.gas.station.resp.PayRespDTO;
import com.qd.panda.gas.station.shell.config.ShellConfig;
import com.qd.panda.gas.station.shell.req.last.ShellPayNotifyReqDTO;
import com.qd.panda.gas.station.shell.req.last.ShellPayRefundNotifyReqDTO;
import com.qd.panda.gas.station.shell.resp.ShellResponseDTO;
import com.qd.panda.gas.station.shell.resp.last.GasStationDTO;
import com.qd.tfpay.util.SignUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Primary;
import org.springframework.http.*;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Component;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.Supplier;

/**
 * 此接口完成壳牌普通加油站的对接
 *
 * @author sjk
 * @create 2022-10-08 15:00
 * @since 1.0.0
 */
@Component
@Slf4j
@RequiredArgsConstructor
@Primary
public class LastShellHttpServiceImpl extends GasHttpService {
    /**
     * 获取站点列表
     */
    public static final String STATIONS_URL = "/n/a/open/oil/stations?partnerId={partnerId}&timestamp={timestamp}&sign={sign}";
    /**
     * 获取站点详情
     */
    public static final String STATION_ID_URL = "partnerId={partnerId}&timestamp={timestamp}&sign={sign}";
    /**
     * 支付回调
     */
    private static final String PAY_NOTIFY_URL = "/n/a/open/oil/pay/notify";
    /**
     * 退款
     */
    private static final String REFUND_NOTIFY_URL = "/n/a/open/oil/refund/notify";
    /**
     * 余额
     */
    private static final String BALANCE_URL = "/n/a/open/oil/balance?partnerId={partnerId}&timestamp={timestamp}&sign={sign}";


    private final RestTemplate restTemplate;
    private final ObjectMapper objectMapper;
    @Resource
    private ShellConfig shellConfig;

    @Override
    public String stationCompanyId() {
        return GasStationCompanyNameEnum.EXTEND_SHELL.getCode();
    }

    @Override
    public PayRespDTO pay(PayReqDTO payReqDTO) {
        throw new ApiException("新新壳牌接口不支持创建订单操作");
    }

    /**
     * 加油支付回调
     *
     * @param base 回执参数
     * @return 成功 or 失败
     * <p>
     * ResourceAccessException.class 只重试IO异常的情况
     * * maxAttempts 最大请求次数，默认为3
     * * 时间间隔(秒) 0.5, 1,2 ...
     * </p>
     */
    @Override
    @Retryable(value = ResourceAccessException.class, maxAttempts = 4, backoff = @Backoff(delay = 500L, multiplier = 2))
    @SneakyThrows
    public boolean payNotify(PayNotifyReqDTO base) {
        ShellPayNotifyReqDTO shellPayNotifyReqDTO = new ShellPayNotifyReqDTO();
        shellPayNotifyReqDTO
                .setNozzleNumber(base.getNozzleNumber())
                .setOctaneNumber(base.getOctaneNumber())
                .setTotalAmount(new BigDecimal(base.getPrice().stripTrailingZeros().toPlainString()))
                .setRealAmount(new BigDecimal(base.getPayAmount().stripTrailingZeros().toPlainString()))
                .setStationId(base.getStationId())
                .setVendorSn(base.getThirdPartyOrderId())
                .setMobile(base.getMobile());
        Map<String, Object> param = objectMapper.convertValue(shellPayNotifyReqDTO, new TypeReference<>() {
        });
        String url = shellConfig.getUrl() + PAY_NOTIFY_URL;
        buildSign(param, null);
        shellPayNotifyReqDTO.setPartnerId(param.get("partnerId").toString());
        shellPayNotifyReqDTO.setTimestamp(param.get("timestamp").toString());
        shellPayNotifyReqDTO.setSign(param.get("sign").toString());
        String reqJson = JacksonUtil.getObjectMapper().writeValueAsString(shellPayNotifyReqDTO);
        log.info("新新壳牌,加油支付回调,url: {},参数: {}", url, reqJson);
        final SendLogDTO sendLog = base.getSendLog();
        if (null != sendLog) {
            sendLog.setUrl(url);
            sendLog.setReqMethod(HttpMethod.POST.name());
            sendLog.setReqParam(reqJson);
        }
        HttpEntity<Object> httpEntity = new HttpEntity<>(shellPayNotifyReqDTO);
        JsonNode responseJson = build(() -> restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class, shellConfig.getNormalKey()));
        base.setData(responseJson.get("data").asText());
        shellPayNotifyReqDTO.freeData();
        return true;
    }

    /**
     * 退款支付回调
     *
     * @param base 退款参数
     */
    @SneakyThrows
    @Override
    public void refundRequests(RefundRequestsReqDTO base) {
        ShellPayRefundNotifyReqDTO shellPayRefundNotifyReqDTO = new ShellPayRefundNotifyReqDTO();
//        shellPayRefundNotifyReqDTO.setVendorSn(base.getOrderId());
        shellPayRefundNotifyReqDTO.setOrderSn(base.getThirdPartyOrderId());
        Map<String, Object> param = objectMapper.convertValue(shellPayRefundNotifyReqDTO, new TypeReference<>() {
        });
        String url = shellConfig.getUrl() + REFUND_NOTIFY_URL;
        buildSign(param, null);
        shellPayRefundNotifyReqDTO.setPartnerId(param.get("partnerId").toString());
        shellPayRefundNotifyReqDTO.setTimestamp(param.get("timestamp").toString());
        shellPayRefundNotifyReqDTO.setSign(param.get("sign").toString());
        String reqJson = JacksonUtil.getObjectMapper().writeValueAsString(shellPayRefundNotifyReqDTO);
        log.info("新新壳牌退款申请,url: {},参数: {}", url, reqJson);
        final SendLogDTO sendLog = base.getSendLog();
        if (null != sendLog) {
            sendLog.setUrl(url);
            sendLog.setReqMethod(HttpMethod.POST.name());
            sendLog.setReqParam(reqJson);
        }
        HttpEntity<Object> httpEntity = new HttpEntity<>(shellPayRefundNotifyReqDTO);
        build(() -> restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class, shellConfig.getNormalKey()));
        shellPayRefundNotifyReqDTO.freeData();
    }

    /**
     * 获取站点列表
     *
     * @return 加油站列表
     */
    @SneakyThrows
    public List<GasStationDTO> getStations(SendLogDTO sendLogDTO) {
        JsonNode jsonNode = stations(sendLogDTO);
        log.info("新新壳牌获取站点列表: {}", jsonNode);
        if (null != jsonNode) {
            return objectMapper.readValue(jsonNode.get("list").toString(), new TypeReference<>() {
            });
        }
        return Collections.emptyList();
    }

    /**
     * 获取站点详细信息
     *
     * @return 加油站列表
     */
    @SneakyThrows
    public GasStationDTO getStationsById(StationDetailReqDTO stationDetailReqDTO) {
        JsonNode jsonNode = getStationById(stationDetailReqDTO);
        log.info("新新壳牌获取站点详细信息: {}", jsonNode);
        if (null != jsonNode) {
            return objectMapper.readValue(jsonNode.get("data").toString(), GasStationDTO.class);
        }
        return null;
    }

    @Override
    public JsonNode stations(SendLogDTO sendLogDTO) {
        ArrayNode arrayNode = objectMapper.createArrayNode();
        int pageNumber = 0;
        int pageSize = 300;
        while (true) {
            JsonNode stations = stations(sendLogDTO, ++pageNumber, pageSize);
            ArrayNode listJson = (ArrayNode) stations.get("list");
            if (null == listJson || listJson.isEmpty()) {
                break;
            } else {
                arrayNode.addAll(listJson);
            }
        }
        ObjectNode objectNode = objectMapper.createObjectNode();
        objectNode.set("list", arrayNode);
        return objectNode;
    }

    @Override
    public JsonNode stations(SendLogDTO sendLogDTO, int pageNumber, int pageSize) {
        Map<String, Object> param = new HashMap<>(4);
        param.put("page", pageNumber);
        param.put("pageSize", pageSize);
        final String url = buildSign(param, shellConfig.getUrl() + STATIONS_URL + "&page=" + pageNumber + "&pageSize=" + pageSize);

        sendLogDTO.setReqMethod(HttpMethod.GET.name());
        sendLogDTO.setUrl(url);
        log.info("stations,url: {}", url);
        HttpEntity<Object> httpEntity = new HttpEntity<>(new HttpHeaders());
        return build(() -> restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class, shellConfig.getNormalKey()));
    }

    @Override
    public JsonNode getStationById(StationDetailReqDTO base) {
        Map<String, Object> param = new HashMap<>();
        final String url = buildSign(param, shellConfig.getUrl() + "/n/a/open/oil/station/" + base.getStationId() + "?" + STATION_ID_URL);
        log.info("新新壳牌,查询站点详细信息,url: {}", url);
        final SendLogDTO sendLog = base.getSendLog();
        if (null != sendLog) {
            sendLog.setUrl(url);
            sendLog.setReqMethod(HttpMethod.GET.name());
        }
        HttpEntity<Object> httpEntity = new HttpEntity<>(new HttpHeaders());
        try {
            return build(() -> restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class, shellConfig.getNormalKey()));
        } catch (Exception e) {
            log.error("新新壳牌查询站点信息异常");
            return null;
        }
    }

    @Override
    public BigDecimal balance(BalanceReqDTO base) {
        Map<String, Object> param = new HashMap<>();
        final String url = buildSign(param, shellConfig.getUrl() + BALANCE_URL);
        log.info("新新壳牌,查询余额,url: {}", url);
        final SendLogDTO sendLog = base.getSendLog();
        if (null != sendLog) {
            sendLog.setUrl(url);
            sendLog.setReqMethod(HttpMethod.POST.name());
        }
        HttpEntity<Object> httpEntity = new HttpEntity<>(new HttpHeaders());
        JsonNode jsonNode = build(() -> restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class, shellConfig.getNormalKey()));
        log.info("jsonNode: {}", jsonNode);
        return jsonNode.get("data").decimalValue();
    }

    public JsonNode build(Supplier<ResponseEntity<String>> supplier) {
        return BaseHttpResponseUtil.build(supplier, this::buildShellResponseDTO, Arrays.asList(HttpStatus.OK.value(), HttpStatus.CREATED.value())).getData();
    }


    private ShellResponseDTO buildShellResponseDTO(JsonNode jsonNode) {
        if (null != jsonNode) {
            ShellResponseDTO shellResponseDTO = new ShellResponseDTO();
            final JsonNode codeJsonNode = Optional.ofNullable(jsonNode.get("code")).orElseThrow(() -> new ApiException("code不存在,json：" + jsonNode));
            final JsonNode messageJsonNode = Optional.ofNullable(jsonNode.get("message")).orElseThrow(() -> new ApiException("message不存在,json：" + jsonNode));
            shellResponseDTO.setErrCode(codeJsonNode.intValue());
            shellResponseDTO.setErrMsg(messageJsonNode.asText());
            shellResponseDTO.setData(jsonNode);
            return shellResponseDTO;
        }
        return null;
    }


    public String buildSign(Map<String, Object> param, String url) {
        long timestamp = System.currentTimeMillis();
        param.put("partnerId", shellConfig.getNormalKey());
        param.put("timestamp", timestamp);
        //2.除去数组中的空值和签名参数
        Map<String, String> newParam = SignUtil.paraFilter(param);
        //3.把数组所有元素排序，并按照“参数=参数值”的模式用“&”字符拼接成字符串 + key
        final String linkString = SignUtil.createLinkString(newParam) + shellConfig.getNormalSecurity();
        if(log.isDebugEnabled()) {
            log.debug("normalSecurity: {}", shellConfig.getNormalSecurity());
            log.debug("【MD5】加密前原始串：{}", linkString);
        }
        //4.生成签名串MD5
        //16进制  UTF-8  小写
        String sign = SecureUtil.md5(linkString);
        if(log.isDebugEnabled()) {
            log.debug("【MD5】加密后sign:{}", sign);
        }
        param.put("sign", sign);
        if (null == url) {
            return null;
        }
        url = url.replace("{partnerId}", shellConfig.getNormalKey());
        url = url.replace("{timestamp}", String.valueOf(timestamp));
        url = url.replace("{sign}", sign);
        return url;
    }
}